package service

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/golang/protobuf/ptypes/empty"
	"github.com/smartwalle/alipay/v3"
	"net/url"
	"pay/api/pay"
	"pay/internal/conf"
	"pay/internal/constant"
	"pay/internal/model"
	"pay/pkg/ecode"
	"pay/pkg/request_key"
	"pay/pkg/wechat"
	"strings"
	"time"
)

// 支付宝token定时刷新
func (s *Service) AlipayTokenRefresh(ctx context.Context, _ *empty.Empty) (resp *pay.DefaultResp, err error) {
	var tokens []*model.AlipayToken
	if tokens, err = s.dao.FindAlipayExpiringTokens(ctx); err != nil {
		return
	}
	if len(tokens) != 0 {
		go func() {
			newCtx := request_key.NewContextWithReqID(ctx)
			var newErr error
			defer ecode.AsyncFailOver(newCtx, "支付宝token刷新后处理", conf.Conf.Env, &newErr)
			for _, tokenConfig := range tokens {
				var alipayResp *alipay.OpenAuthTokenAppRsp
				if alipayResp, err = s.downstream.AlipayClient.OpenAuthTokenApp(alipay.OpenAuthTokenApp{
					GrantType:    "refresh_token",
					RefreshToken: tokenConfig.RefreshToken,
				}); err != nil {
					err = ecode.AppendMessage(constant.ErrRemoteError, "支付宝授权失败: %s", err.Error())
					return
				}
				token := alipayResp.Content.Tokens[0]
				if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
					// 更新
					tokenConfig.UserId = token.UserId
					tokenConfig.Token = token.AppAuthToken
					tokenConfig.ExpireTime = time.Now().Add(time.Duration(token.ExpiresIn) * time.Second)
					tokenConfig.RefreshToken = token.AppRefreshToken
					tokenConfig.Status = true
					if err = s.dao.Save(ctx, tokenConfig); err != nil {
						return
					}
					return
				}); err != nil {
					return
				}
			}
		}()
	}
	resp = &pay.DefaultResp{Status: true}
	return
}

func getNextExecuteTime(message *model.Schedule) time.Duration {
	switch pay.ScheduleType(message.ScheduleType) {
	case pay.ScheduleType_SCHEDULE_TYPE_PAY_QUERY:
		if message.Retry <= 6 {
			// 前1分钟，10秒查询一次
			return time.Second * 10
		} else {
			return time.Minute * time.Duration((message.Retry-6)^2)
		}
	}
	return time.Minute * time.Duration(message.Retry^2)
}

// 定时查询(触发重试nsq消息，不落nsq重试任务表)
func (s *Service) ScheduleRetry(ctx context.Context, req *pay.CommonRetryRequest) (resp *pay.DefaultResp, err error) {
	resp = &pay.DefaultResp{Status: true}
	limit := int(req.Limit)
	if limit == 0 {
		limit = 1000
	}
	var (
		messages  []*model.Schedule
		needAlert bool
		alertKeys []string
	)
	if messages, err = s.dao.FindRetrySchedule(ctx, limit); err != nil {
		return
	}
	if len(messages) != 0 {
		nsqMessages := make([][]byte, len(messages))
		for i, message := range messages {
			var msg json.RawMessage
			if msg, err = json.Marshal(&pay.PayOrderScheduleMessage{
				Type:    pay.ScheduleType(message.ScheduleType),
				OrderId: message.OrderId,
			}); err != nil {
				return
			}
			nsqMessages[i] = msg
			message.Retry += 1
			message.ExecuteTime = time.Now().Add(getNextExecuteTime(message))
			if message.Retry > 10 {
				needAlert = true
				alertKeys = append(alertKeys, fmt.Sprintf(
					"%d-%d", message.ScheduleType, message.OrderId))
			}
			if err = s.dao.AutoUpdate(ctx, message); err != nil {
				return
			}
		}
		if err = s.dao.MultiPublishWithoutTransaction(
			ctx, constant.TopicInnerOrder, nsqMessages); err != nil {
			return
		}
	}
	if needAlert {
		alertKey := "pay/alert/schedule"
		if s.dao.RedisClient.Exists(alertKey).Val() == 0 {
			wechat.AsyncSendMarkDown(ctx, wechat.FormatBugMessage(ctx, conf.Conf.Env, conf.Conf.ServiceName,
				"schedule", fmt.Sprintf("定时任务执行存在超过10次的数据，请及时查看"),
				strings.Join(alertKeys, ",")))
			s.dao.RedisClient.Set(alertKey, 1, time.Hour*1)
		}
	}
	resp = &pay.DefaultResp{Status: true}
	return
}

func (s *Service) MailWechatAccount(ctx context.Context, _ *empty.Empty) (resp *pay.DefaultResp, err error) {
	var (
		configs           []*model.WechatConfig
		sgNameMap, appMap map[uint32]string
		sgIds             []int64
	)
	resp = &pay.DefaultResp{Status: true}
	if configs, err = s.dao.FindPreActivatedWechatConfigs(ctx); err != nil {
		return
	}
	if len(configs) == 0 {
		return
	}
	sgMap := make(map[uint32]bool)
	for _, config := range configs {
		if _, ok := sgMap[config.SgId]; !ok {
			sgMap[config.SgId] = true
			sgIds = append(sgIds, int64(config.SgId))
		}
	}
	if sgNameMap, err = s.downstream.GetBrandNameMap(ctx, sgIds); err != nil {
		return
	}
	if appMap, err = s.downstream.QueryAppIds(ctx, sgIds); err != nil {
		return
	}
	dataMap := make([]map[string]string, len(configs))
	for i, config := range configs {
		dataMap[i] = make(map[string]string)
		for _, header := range constant.ReviewHeaders {
			switch header {
			case "品牌":
				dataMap[i][header] = sgNameMap[config.SgId]
			case "别名":
				dataMap[i][header] = config.Name
			case "微信子商户号":
				dataMap[i][header] = config.MerchantId
			case "小程序":
				dataMap[i][header] = appMap[config.SgId]
			case "操作":
				// link := conf.Conf.Domain + url.QueryEscape(fmt.Sprintf("/pay/common/wechat/activate?id=%d&mch_id=%s&sg_id=%d&p_store_id=%d",
				// 	config.ID, config.MerchantId, config.SgId, config.PStoreId))
				// 这里有个奇怪的bug,用飞书发邮件mch_id=abc,这里的等号会被吃掉,所以用了两个等号
				link := fmt.Sprintf("%s/pay/common/wechat/activate?id=%d&mch_id==%s&sg_id=%d&p_store_id=%d",
					conf.Conf.Domain, config.ID, url.QueryEscape(string(config.MerchantId)), config.SgId, config.PStoreId)
				dataMap[i][header] = fmt.Sprintf(
					"<a href=\"%s\">已完成配置</a>", link)
			}
		}
	}
	s.dao.MailClient.AsyncSend(
		ctx, s.dao.MailClient.FormatMessage(
			fmt.Sprintf("%s微信收款账户待审核信息", conf.Conf.ProdName),
			s.dao.MailClient.MakeHTMLTables(
				"微信收款账户与小程序绑定待完成配置如下", constant.ReviewHeaders, dataMap),
			[]string{conf.Conf.MailConfig.Maintain}, conf.Conf.Env), conf.Conf.Env)
	return
}
